/** Apply.java.

	Purpose:
		
	Description:
		
	History:
		12:49:11 PM Oct 22, 2014, Created by jumperchen

Copyright (C) 2014 Potix Corporation. All Rights Reserved.
 */
package org.zkoss.zuti.zul;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.zkoss.bind.Binder;
import org.zkoss.bind.impl.BinderUtil;
import org.zkoss.bind.sys.ReferenceBinding;
import org.zkoss.lang.Objects;
import org.zkoss.util.Maps;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.Templates;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.ext.DynamicPropertied;
import org.zkoss.zk.ui.metainfo.impl.ShadowDefinitionImpl;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.sys.PropertyAccess;
import org.zkoss.zk.ui.sys.StringPropertyAccess;
import org.zkoss.zk.ui.util.Template;

/**
 * The apply tag allows you to choose which <tt>template</tt> to be
 * applied. You specify the template's name using the <tt>template</tt> attribute
 * or the template's URI using the <tt>templateURI</tt> attribute.
 *
 * <h3>Passing Parameters</h3>
 *
 * <p>There are two ways to pass parameters to the apply shadow:
 * <p>First, you can use {@link #setDynamicProperty}, or, in ZUL,
 * <pre><code>&lt;apply templateURI="/WEB-INF/mypage" arg="something"/&gt;</code></pre>
 *
 * <p>Second, you can use the query string:
 * <pre><code>&lt;apply templateURI="/WEB-INF/mypage?arg=something"/&gt;</code></pre>
 *
 * <p>With the query string, you can pass only the String values.
 * and the parameter can be accessed by {@link Execution#getArg()}
 * Or, you can access it with the <tt>arg</tt> variable in EL expressions.
 *
 * @author jumperchen
 * @since 8.0.0
 */
public class Apply extends TemplateBasedShadowElement implements DynamicPropertied {

	private static final long serialVersionUID = 2014102212493116L;

	// refer from ReferenceBindingHandlerImpl#REFERENCE_SET
	private static final String REFERENCE_SET = "$REF_SET$";

	private String _template = "";
	private String _templateURI;
	protected Map<String, Object> _props;

	public Apply() {
		init();
	}

	private void init() {
		_props = new LinkedHashMap<String, Object>();
	}

	/**
	 * Returns the template name
	 * <p> Default: empty string
	 */
	public String getTemplate() {
		return _template;
	}

	/**
	 * Sets the template name to apply.
	 * <p>One cannot set both template and template URI in the same time.</p>
	 *
	 * <p>Notice that, the template enclosed with &lt;apply&gt; tag will be created
	 * only when both template and template URI are not set.</p>
	 */
	public void setTemplate(String template) {
		if (template != null && template.length() > 0 && _templateURI != null && _templateURI.length() > 0) {
			throw new UiException("Can not set template and template uri in the same time. "
					+ "Set template uri as null or empty string first.");
		}
		if (!Objects.equals(_template, template)) {
			_template = template;
			_dirtyBinding = true;
		}
	}

	/**
	 * Sets the template uri.
	 *
	 * <p>If templateURI is changed, the whole shadow is recreated.
	 * One cannot set both template and template URI in the same time.</p>
	 *
	 * <p>Notice that, the template enclosed with &lt;apply&gt; tag will be created
	 * only when both template and template URI are not set.</p>
	 *
	 * @param templateURI the template URI. If null or empty, nothing is applied.
	 * You can specify the template URI with the query string and they
	 * will become a map of parameters that is accessible by the arg variable
	 * in EL, or by {@link Execution#getArg}.
	 * For example, if "/a.zul?b=c" is specified, you can access
	 * the parameter with ${arg.b} in a.zul.
	 * @see #setDynamicProperty
	 */
	public void setTemplateURI(String templateURI) {
		if (templateURI != null && templateURI.length() > 0 && _template != null && _template.length() > 0) {
			throw new UiException("Can not set template and template uri in the same time. "
					+ "Set template as null or empty string first.");
		}
		if (!Objects.equals(_templateURI, templateURI)) {
			_templateURI = templateURI;
			_dirtyBinding = true;
		}
	}

	public String getTemplateURI() {
		return _templateURI;
	}

	/**
	 * Return the template, if any, the default implementation will look up 
	 * the template where in its parent shadow or its
	 * shadow host.
	 * @return Template
	 */
	protected Template resolveTemplate() {
		if (_templateURI != null && _templateURI.length() > 0) //if template uri is set, not possible to have template
			return null;
		else {
			return lookupTemplate(this, this, getTemplate(), null);
		}
	}

	private Template lookupTemplate(Component comp, Apply base, String name, Component compBase) {
		return Templates.lookup(comp, base, name, compBase);
	}

	public Object resolveVariable(Component child, String name, boolean recurse) {
		Object result = _props.get(name);
		if (result == null)
			return super.resolveVariable(child, name, recurse);
		else
			return result;
	}

	/**
	 * Composes from template name and then template uri, if any.
	 */
	@SuppressWarnings("unchecked")
	protected void compose(Component host) {
		final Execution exec = Executions.getCurrent();
		if (exec == null)
			throw new IllegalStateException("No execution available");

		Template t = resolveTemplate();

		if (t != null) {
			// support "arg" properties to the template
			exec.pushArg(_props);
			try {
				t.create(host, getNextInsertionComponentIfAny(), null, null);
			} finally {
				exec.popArg();
			}
		}
		String templateURI = _templateURI;
		if (templateURI == null) {
			templateURI = ((ShadowDefinitionImpl) getDefinition()).getTemplateURI();
		}
		if (templateURI != null) {
			String uri = templateURI;
			final int queryStart = uri.indexOf('?');
			String queryString = null;
			if (queryStart >= 0) {
				queryString = uri.substring(queryStart + 1);
				uri = uri.substring(0, queryStart);
			}
			Map<String, String> map = (Map<String, String>) Maps.parse(null, queryString, '&', '=', false);
			Map<Object, Object> arg = new HashMap<Object, Object>();
			arg.putAll(_props);
			arg.putAll(map);
			exec.createComponents(uri, host, getNextInsertionComponentIfAny(), null, arg);
		}
		Set<String> refs = (Set<String>) this.getAttribute(REFERENCE_SET);
		if (refs != null && !refs.isEmpty()) {
			Binder binder = BinderUtil.getBinder(this);
			for (Component comp : getDistributedChildren()) {
				if (refs != null)
					copyReferenceBinding(comp, refs, binder);
			}
		}
	}

	private void copyReferenceBinding(Component comp, Set<String> refs, Binder binder) {
		for (String key : refs) {
			addReferenceBinding(comp, key, (ReferenceBinding) getAttribute(key));
		}
	}

	private void addReferenceBinding(Component comp, String attr, ReferenceBinding binding) {
		comp.setAttribute(attr, binding);
		Set<String> refs = (Set<String>) comp.getAttribute(REFERENCE_SET, COMPONENT_SCOPE);
		if (refs == null) {
			comp.setAttribute(REFERENCE_SET, refs = new HashSet<String>());
		}
		refs.add(attr);
	}

	protected boolean isEffective() {
		final ComponentCtrl compCtrl = (ComponentCtrl) this;
		if (!compCtrl.getAnnotatedProperties().isEmpty() && !isBindingReady())
			return false;
		final boolean hasTemplate = !("".equals(_template) && getTemplate("") == null);
		return hasTemplate || _templateURI != null || ((ShadowDefinitionImpl) getDefinition()).getTemplateURI() != null;
	}

	//-- DynamicPropertied --//
	public boolean hasDynamicProperty(String name) {
		return _props.containsKey(name);
	}

	public Object getDynamicProperty(String name) {
		return _props.get(name);
	}

	public Map<String, Object> getDynamicProperties() {
		return _props;
	}

	public void setDynamicProperty(String name, Object value) throws WrongValueException {
		_dirtyBinding = true;
		_props.put(name, value);
	}

	//--ComponentCtrl--//
	private static HashMap<String, PropertyAccess> _properties = new HashMap<String, PropertyAccess>(2);

	static {
		_properties.put("template", new StringPropertyAccess() {
			public void setValue(Component cmp, String value) {
				((Apply) cmp).setTemplate(value);
			}

			public String getValue(Component cmp) {
				return ((Apply) cmp).getTemplate();
			}
		});
		_properties.put("templateURI", new StringPropertyAccess() {
			public void setValue(Component cmp, String value) {
				((Apply) cmp).setTemplateURI(value);
			}

			public String getValue(Component cmp) {
				return ((Apply) cmp).getTemplateURI();
			}
		});
	}

	public PropertyAccess getPropertyAccess(String prop) {
		PropertyAccess pa = _properties.get(prop);
		if (pa != null)
			return pa;
		return super.getPropertyAccess(prop);
	}

	//Cloneable//
	public Object clone() {
		final Apply clone = (Apply) super.clone();
		clone.init();
		clone._props.putAll(_props);
		return clone;
	}
}
